package com.reus.datasource;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.LinkedList;
import java.util.Queue;
import java.util.logging.Logger;

/**
 * description: 池化线程池,对连接进行管理
 * copyright: Copyright (c) 2018-2021
 * company: iSysCore Tech. Co., Ltd.
 *
 * @author liuxq@isyscore.com
 * @version 1.0
 * @date 2021-10-18 17:36:08
 */
public class PoolDataSource implements DataSource {

    /**
     * description: 最大活跃线程数，此处可根据实际情况自行设置
     *
     * @modified liuxq@isyscore.com
     * @date 2021-10-18
     */
    private Integer maxActiveConnectCount = 10;

    /**
     * description: 最大空闲线程数，此处可根据实际情况自行设置
     *
     * @modified liuxq@isyscore.com
     * @date 2021-10-18
     */
    private Integer maxIdleConnectCount = 10;

    /**
     * description: 连接最长使用时间，在自定义连接中配置了连接开始时间，在这里来判断该连接是否超时
     *
     * @modified liuxq@isyscore.com
     * @date 2021-10-18
     */
    private Long maxConnectTime = 30 * 1000L;

    /**
     * description: 线程wait等待时间
     *
     * @modified liuxq@isyscore.com
     * @date 2021-10-18
     */
    private Integer waitTime = 2000;

    /**
     * description: 使用normalDataSource来产生连接
     *
     * @modified liuxq@isyscore.com
     * @date 2021-10-18
     */
    private NormalDataSource normalDataSource;

    /**
     * description: 存放活跃连接的队列
     *
     * @modified liuxq@isyscore.com
     * @date 2021-10-18
     */
    private Queue<PoolConnection> activeConList = new LinkedList<>();

    /**
     * description: 存放空闲连接的队列
     *
     * @modified liuxq@isyscore.com
     * @date 2021-10-18
     */
    private Queue<PoolConnection> idleConList = new LinkedList<>();

    public PoolDataSource(String driverClassName, String url, String userName, String passWord) {

        this(driverClassName, url, userName, passWord, 10, 10);

    }

    public PoolDataSource(String driverClassName, String url, String userName, String passWord, Integer maxActiveConnectCount, Integer maxIdleConnectCount) {
        // 初始化normalDataSource，因为normalDataSource已经封装了新建连接的方法
        this.normalDataSource = new NormalDataSource(driverClassName, url, userName, passWord);
        this.maxActiveConnectCount = maxActiveConnectCount;
        this.maxIdleConnectCount = maxIdleConnectCount;

    }

    /**
     * @Desc 获取连接时先从空闲连接列表中获取。若没有，则判断现在活跃连接是否已超过设置的最大活跃连接数，没超过，new一个
     * 若超过，则判断第一个连接是否已超时，若超时，则移除掉在新建。若未超时，则wait(）等待。
     **/
    @Override
    public Connection getConnection() {
        Connection connection;
        try {
            connection = doGetPoolConnection().connection;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return connection;
    }

    public void removeConnection(Connection connection) {
        PoolConnection poolConnection = new PoolConnection(connection);
        doRemovePoolConnection(poolConnection);
    }

    private PoolConnection doGetPoolConnection() throws SQLException {
        PoolConnection connection = null;
        while (connection == null) {
            // 加锁
            synchronized (this) {
                // 判断是否有空闲连接
                if (idleConList.size() < 1) {
                    // 判断活跃连接数是否已经超过预设的最大活跃连接数
                    if (activeConList.size() < maxActiveConnectCount) {
                        // 如果还可以新建连接，就新建一个连接
                        connection = new PoolConnection(normalDataSource.getConnection());
                    } else {
                        // 走到这一步，说明没有空闲连接，并且活跃连接已经满了，则只能看哪些连接超时，判断第一个连接是否超时
                        PoolConnection poolConnection = activeConList.peek();
                        // 这就是我为啥要给数据库连接加连接时间了
                        if (System.currentTimeMillis() - poolConnection.getCheckOutTime() > maxConnectTime) {
                            // 若第一个连接已经超时了，移除第一个活跃连接
                            PoolConnection timeOutConnect = activeConList.poll();
                            if (!timeOutConnect.connection.getAutoCommit()) {
                                // 如果该连接设的是非自动提交，则对事物进行回滚操作
                                timeOutConnect.connection.rollback();
                            }
                            // 置为空，让垃圾收集器去收集
                            timeOutConnect.connection.close();
                            timeOutConnect = null;
                            // 新建一个连接
                            connection = new PoolConnection(normalDataSource.getConnection());
                        } else {
                            // 走到这一步，代表所有连接都没有超时，只能等了
                            try {
                                // 于是等一会
                                this.wait(waitTime);
                            } catch (InterruptedException e) {
                                // ignore错误，并退出
                                break;
                            }
                        }
                    }
                } else {
                    // 这一步代表空闲连接队列里面有连接，则直接取出
                    connection = idleConList.poll();
                }
                if (connection != null) {
                    // 设置这个连接的连接时间
                    connection.setCheckOutTime(System.currentTimeMillis());
                    // 然后放入活跃连接队列中
                    activeConList.add(connection);
                }
            }
        }

        return connection;
    }

    /**
     * description: 移除连接
     *
     * @param connection:
     * @modified liuxq@isyscore.com
     * @date 2021-10-18
     * @return: void
     */
    private void doRemovePoolConnection(PoolConnection connection) {
        // 加锁
        synchronized (this) {
            // 移除连接，这里需要重写PoolConnection的equal方法，因为只要真实连接相同，就可以代表这个封装的连接相等了
            activeConList.remove(connection);
            if (idleConList.size() < maxIdleConnectCount) {
                // 加入空闲连接队列中
                idleConList.add(connection);
            }
            // 通知其他等待线程，让他们继续获取连接
            this.notifyAll();
        }
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return getConnection();
    }

    /**
     * description: 后面的方法没有用到就没有重写了
     *
     * @param iFace:
     * @modified liuxq@isyscore.com
     * @date 2021-10-18
     * @return: T
     */
    @Override
    public <T> T unwrap(Class<T> iFace) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iFace) throws SQLException {
        return false;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
}
